Skip to content

consensus: fix integer overflow in ValidateCollateralRatio()#373

Closed
gto90 wants to merge 10 commits intofeature/digidollar-v1from
fix/digidollar-sec-001-integer-overflow
Closed

consensus: fix integer overflow in ValidateCollateralRatio()#373
gto90 wants to merge 10 commits intofeature/digidollar-v1from
fix/digidollar-sec-001-integer-overflow

Conversation

@gto90
Copy link
Member

@gto90 gto90 commented Feb 13, 2026

Motivation

Fixes DGB-SEC-001 from PR #367 security audit. ValidateCollateralRatio() performs collateralAmount * oraclePrice which overflows int64_t for large but legitimate values (e.g. 1M DGB at $10/DGB = 10^20 > INT64_MAX 9.2x10^18). The same overflow exists for ddAmount * requiredRatio.

Description

src/consensus/digidollar_transaction_validation.cpp:

  • Added overflow-safe multiplication using the check-before-multiply pattern (consistent with dca.cpp:60-70)
  • Before multiplying, checks if the value exceeds INT64_MAX / multiplier
  • Falls back to divide-first-then-multiply when overflow would occur

src/digidollar/validation.cpp:

  • Same overflow protection for dgbLocked * ctx.oraclePriceMicroUSD
  • Added ddMinted > 0 guard to prevent division by zero in ratio calculation

src/test/digidollar_transaction_tests.cpp:

  • Added test_collateral_ratio_overflow_protection test case
  • Tests normal values, large collateral (1M DGB), MAX_MONEY collateral, large DD amounts, and negative/zero edge cases

Testing

  • All 36 DigiDollar transaction tests pass
  • Full test suite: 2 pre-existing failures in i2p_tests (unrelated)
  • All 3 modified files compile cleanly (only pre-existing warnings)

Refs: PR #367 (DGB-SEC-001)

Copilot AI review requested due to automatic review settings February 13, 2026 03:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request addresses DGB-SEC-001, a critical integer overflow vulnerability in ValidateCollateralRatio() identified in security audit PR #367. The vulnerability occurs when multiplying collateralAmount * oraclePrice or ddAmount * requiredRatio, which can exceed INT64_MAX for large but legitimate values (e.g., 1M DGB at high prices).

Changes:

  • Added overflow-safe multiplication using check-before-multiply pattern in two validation functions
  • Implemented divide-first fallback when multiplication would overflow, consistent with existing pattern in dca.cpp
  • Added comprehensive test coverage for overflow scenarios including normal, large, and MAX_MONEY values

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/consensus/digidollar_transaction_validation.cpp Added overflow protection for collateralAmount * oraclePrice and ddAmount * requiredRatio multiplications using check-before-multiply pattern
src/digidollar/validation.cpp Added overflow protection for dgbLocked * ctx.oraclePriceMicroUSD and division-by-zero guard for ddMinted > 0
src/test/digidollar_transaction_tests.cpp Added test case test_collateral_ratio_overflow_protection covering normal values, large collateral, MAX_MONEY, and edge cases

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Use $$ deferred evaluation for CFLAGS/CPPFLAGS assignments so
OS-specific flags (cflags_linux, cppflags_linux) appended by funcs.mk
after set_vars are included. Route -fPIC and -D_GNU_SOURCE through
proper cflags/cppflags variables instead of bare config_opts to avoid
OpenSSL's "Mixing make variables" rejection.
The depends system never defines $(package)_arflags, so
ARFLAGS=$($(package)_arflags) in config_env exported an empty
ARFLAGS="" to the environment. When CFLAGS/CPPFLAGS are passed
as VAR=value assignments (not positional flags), OpenSSL 1.1.1w's
Configure sets $anyuseradd=false and falls back to reading env
vars. The empty ARFLAGS overrides the target default "r" from
Configurations/00-base-templates.conf, producing a Makefile with
ARFLAGS= (empty). This causes "ar: two different operation options
specified" on Linux and "ar: illegal option -- /" on macOS during
build_libs, as ar interprets the archive path as flags.

Fix: remove ARFLAGS from config_env entirely, letting Configure
use its target default ARFLAGS="r".

Verified locally:
- Linux x86_64: ar r apps/libapps.a ... (ARFLAGS=r in Makefile)
- macOS ARM64: ar r apps/libapps.a ... (ARFLAGS=r in Makefile)
- Full build_libs completes successfully on darwin64-arm64-cc
Replace BOOST_CHECK with BOOST_CHECK_MESSAGE in 5 mint validation
tests that fail on CI but pass locally. Diagnostic output captures
the exact reject reason, script sizes, collateral amounts, and
script type identification to help debug the CI-specific failure.
Replace ClearFreeze() with ClearHistory() in the validation test
fixture. ClearFreeze() only clears freeze flags, but UpdateState()
(called during ValidateDigiDollarTransaction) recalculates volatility
from stale price history left by earlier test suites (e.g.,
digidollar_health_tests) and re-sets the freeze. This caused all
"valid mint" tests to fail with "minting-frozen-volatility" on CI
where the test suite ordering (linker-dependent) places health_tests
before validation_tests.

ClearHistory() resets price history, volatility state, and all freeze
flags, ensuring each test starts from a clean state.
@gto90 gto90 closed this Feb 13, 2026
@gto90 gto90 reopened this Feb 13, 2026
Previous CI re-run used old code without the fix.
Canceling that and triggering fresh run.
@gto90 gto90 closed this Feb 13, 2026
@gto90 gto90 reopened this Feb 13, 2026
Trigger fresh CI run with the cross-suite state pollution fix.
Keep our diagnostic BOOST_CHECK_MESSAGE over base branch's
BOOST_TEST_MESSAGE for better CI failure reporting.
The merge with feature/digidollar-v1 silently dropped critical code:

1. DD amount double-counting prevention in ValidateMintTransaction()
   The OP_RETURN and DD token output both encode ddAmount. Without the
   consistency check, totalDD was counted twice (e.g. 10000 + 10000 =
   20000), requiring 2x the collateral and failing all valid mint tests.

2. Missing #include <digidollar/health.h>

3. Improved log format for insufficient-collateral diagnostic

Also adopts the base branch's test file which includes:
- DD OP_RETURN outputs required for T1-04b NUMS verification
- Proper NUMS key usage via GetCollateralNUMSKey()
- ClearHistory() fix for cross-suite volatility state pollution
@DigiSwarm
Copy link

Closing as duplicate — already fixed with a better approach on feature/digidollar-v1.

Why this is superseded:

This PR uses a "divide first to avoid overflow" pattern in ValidateCollateralRatio() and related functions. Our branch already uses __int128 arithmetic for all collateral calculations (commits 090ac9b1fd, 8a169127be, 8de117954a), which is strictly better:

  • No precision loss__int128 gives exact results vs divide-first which loses up to 999 satoshis
  • Simpler code — one cast vs conditional branching
  • Applied everywhere — consensus validation, TxBuilder, RPC calculations, all using the same pattern

The vulnerability was real (SEC-001) and the analysis was correct — we just fixed it differently during the overnight red team session. Thanks @gto90 for identifying the overflow vectors.

@DigiSwarm DigiSwarm closed this Feb 13, 2026
The test at line 4133 created two separate temporary XOnlyPubKey
objects — one for .begin() and another for .end() — producing
iterators into different memory regions. This is undefined behavior
that manifests as std::length_error on some platforms.

Fix: create a named XOnlyPubKey variable and use the existing
MakeP2TR() helper instead of manual CScript construction.
@gto90 gto90 reopened this Feb 13, 2026
@gto90 gto90 closed this Feb 14, 2026
@gto90 gto90 deleted the fix/digidollar-sec-001-integer-overflow branch February 14, 2026 18:18
gto90 added a commit that referenced this pull request Feb 14, 2026
On forked repositories, pull_request events only reliably trigger
CI for PRs targeting the default branch (develop). PRs targeting
non-default branches like feature/digidollar-v1 silently fail to
trigger workflows — confirmed across 7 previous PRs (#373-378, #369)
that all had zero CI runs.

Add feature/** and fix/** to push triggers so CI runs directly on
push. Add workflow_dispatch as a manual fallback from the Actions tab.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants